
    )j                       d Z ddlmZ ddlZddlmZ ddlmZmZm	Z	 ed         Z
 ej        d          ZdZd,dZe	 G d de                      Zd-dZd.dZ G d d          Z G d de          Z G d de          Z G d de          Zd/dZ ed          ZdZdZd Zd Zd0d#Z G d$ d%e          Z G d& d'e          Z G d( d)e          Z  G d* d+          Z!dS )1u=  Abstract service manager interface.

Wraps the existing systemd (Linux host), launchd (macOS host), Windows
Scheduled Task (native Windows host), and s6 (container) backends behind
a common Protocol. Only the s6 backend supports runtime registration
(for per-profile gateways) — host backends raise NotImplementedError
from those methods, and callers MUST check supports_runtime_registration()
before invoking them.

Host-side call sites (setup wizard, uninstall, status) continue to use
the existing module-level functions in hermes_cli.gateway and
hermes_cli.gateway_windows directly. This protocol is a thin facade
used by new code that needs to be backend-agnostic — specifically the
profile create/delete hooks (Phase 4) and the s6 dispatch path in
``hermes gateway start/stop/restart`` when running inside a container.
    )annotationsN)Path)LiteralProtocolruntime_checkable)systemdlaunchdwindowss6nonez^[a-z0-9][a-z0-9_-]*$   namestrreturnNonec                    | st          d          t          |           t          k    r(t          dt          |            dt           d          t                              |           st          d|           dS )aA  Raise ValueError if ``name`` is not usable as a profile name.

    Profile names are used as s6 service directory names, so they must
    match a conservative subset of filesystem-safe characters. Reject
    empty strings, uppercase, paths-traversal sequences, and anything
    longer than s6's default ``name_max``.
    zprofile name must not be emptyzprofile name too long (z > )z1profile name must match [a-z0-9][a-z0-9_-]*, got N)
ValueErrorlen_MAX_PROFILE_LEN_VALID_PROFILE_REmatch)r   s    ?/home/ubuntu/.hermes/hermes-agent/hermes_cli/service_manager.pyvalidate_profile_namer   !   s      ;9:::
4yy###Gc$iiGG4DGGG
 
 	
 ""4(( 
HHH
 
 	

 
    c                  d    e Zd ZU dZded<   ddZdd	Zdd
ZddZddZ	ddddZ
ddZddZdS )ServiceManageru  Abstract interface for init-system-specific service operations.

    Lifecycle methods (start / stop / restart / is_running) are
    implemented by every backend. Runtime registration
    (register_profile_gateway / unregister_profile_gateway /
    list_profile_gateways) is implemented only by the s6 backend —
    callers MUST check ``supports_runtime_registration()`` before
    invoking the registration methods.
    ServiceManagerKindkindr   r   r   r   c                    d S N selfr   s     r   startzServiceManager.startD         r   c                    d S r!   r"   r#   s     r   stopzServiceManager.stopE   r&   r   c                    d S r!   r"   r#   s     r   restartzServiceManager.restartF   r&   r   boolc                    d S r!   r"   r#   s     r   
is_runningzServiceManager.is_runningG   r&   r   c                    d S r!   r"   r$   s    r   supports_runtime_registrationz,ServiceManager.supports_runtime_registrationJ   r&   r   N	extra_envprofiler2   dict[str, str] | Nonec                   d S r!   r"   r$   r3   r2   s      r   register_profile_gatewayz'ServiceManager.register_profile_gatewayK   s	    
 sr   c                    d S r!   r"   r$   r3   s     r   unregister_profile_gatewayz)ServiceManager.unregister_profile_gatewayQ   r&   r   	list[str]c                    d S r!   r"   r/   s    r   list_profile_gatewaysz$ServiceManager.list_profile_gatewaysR   r&   r   r   r   r   r   r   r   r   r+   r   r+   r3   r   r2   r4   r   r   r3   r   r   r   r   r;   )__name__
__module____qualname____doc____annotations__r%   r(   r*   r-   r0   r7   r:   r=   r"   r   r   r   r   5   s            ,+++****----0000 9888
 ,0	      DCCC555555r   r   r   c                     ddl m}  ddlm}m}m}  |             rt                      rdS  |            rdS  |            rdS  |            rdS dS )	ud  Detect which service manager is available in this environment.

    Returns:
        "s6" — inside a container when /init is s6-svscan (Phase 2+)
        "windows" — native Windows host
        "launchd" — macOS host
        "systemd" — Linux host with a working user/system bus
        "none" — anything else (Termux, sandbox shells, etc.)

    This function does NOT replace ``supports_systemd_services()`` —
    host call sites continue to use that. It exists for new backend-
    agnostic code (profile create/delete hooks, the s6 dispatch path
    in ``hermes gateway start/stop/restart``).
    r   )is_container)is_macos
is_windowssupports_systemd_servicesr   r
   r	   r   r   )hermes_constantsrJ   hermes_cli.gatewayrK   rL   rM   _s6_running)rJ   rK   rL   rM   s       r   detect_service_managerrQ   U   s    $ .-----          |~~ +-- tz|| yxzz y  "" y6r   r+   c                     	 t          d                              d                                          } n# t          $ r Y dS w xY w| dk    rdS t          d                                          S )u  True when s6-svscan is running as PID 1 in this container.

    Detection has to work for **both** root and the unprivileged hermes
    user (UID 10000). The obvious probe — ``Path('/proc/1/exe').resolve()``
    — only works as root: for any other UID, the symlink at
    ``/proc/1/exe`` is unreadable and ``resolve()`` silently returns the
    path unchanged, so the resolved name is the literal ``"exe"`` and
    detection always fails. Since every Hermes runtime call inside the
    container drops to hermes via ``s6-setuidgid``, that silent failure
    made the entire service-manager runtime-registration path inert in
    production (PR #30136 review).

    Probe instead via:
      * ``/proc/1/comm`` — world-readable, contains the process comm
        (``s6-svscan`` when s6-overlay is PID 1).
      * ``/run/s6/basedir`` — s6-overlay-specific directory created by
        stage1. World-readable. More specific than ``/run/s6`` (which
        other tools occasionally create).

    Both signals are required; either alone could false-positive
    (e.g. a container with the s6 binaries installed but a different
    init, or an unrelated process named ``s6-svscan``).
    z/proc/1/commzutf-8)encodingFz	s6-svscanz/run/s6/basedir)r   	read_textstripOSErroris_dir)comms    r   rP   rP   y   s    0N##--w-??EEGG   uu{u!""))+++s   58 
AAc                  8    e Zd ZdZddZddddZddZddZdS )_RegistrationUnsupportedMixinz@Mixin for host backends that don't support runtime registration.r   r+   c                    dS )NFr"   r/   s    r   r0   z;_RegistrationUnsupportedMixin.supports_runtime_registration   s    ur   Nr1   r3   r   r2   r4   r   c               J    t          t          |           j         d          )NzO does not support runtime profile gateway registration (container-only feature)NotImplementedErrortyperD   r6   s      r   r7   z6_RegistrationUnsupportedMixin.register_profile_gateway   s1     "Dzz" < < <
 
 	
r   c                J    t          t          |           j         d          )NzQ does not support runtime profile gateway unregistration (container-only feature)r]   r9   s     r   r:   z8_RegistrationUnsupportedMixin.unregister_profile_gateway   s/    !Dzz" > > >
 
 	
r   r;   c                    g S r!   r"   r/   s    r   r=   z3_RegistrationUnsupportedMixin.list_profile_gateways   s    	r   r@   rA   rB   rC   )rD   rE   rF   rG   r0   r7   r:   r=   r"   r   r   rZ   rZ      sy        JJ    ,0		
 	
 	
 	
 	
 	

 
 
 
     r   rZ   c                  B    e Zd ZU dZdZded<   dd	Zdd
ZddZddZ	dS )SystemdServiceManagera	  Thin wrapper around the ``systemd_*`` functions in hermes_cli.gateway.

    Existing host call sites continue to use those functions directly;
    this wrapper exists for new code that needs to be backend-agnostic
    (the Phase 4 profile create/delete hooks).
    r   r   r   r   r   r   r   c                &    ddl m}  |             d S )Nr   )systemd_start)rO   re   )r$   r   re   s      r   r%   zSystemdServiceManager.start   #    444444r   c                &    ddl m}  |             d S )Nr   )systemd_stop)rO   rh   )r$   r   rh   s      r   r(   zSystemdServiceManager.stop   #    333333r   c                &    ddl m}  |             d S )Nr   )systemd_restart)rO   rk   )r$   r   rk   s      r   r*   zSystemdServiceManager.restart   (    666666r   r+   c                ,    ddl m}  |            \  }}|S )Nr   )_probe_systemd_service_running)rO   rn   )r$   r   rn   _runnings        r   r-   z SystemdServiceManager.is_running   s+    EEEEEE3355
7r   Nr>   r?   
rD   rE   rF   rG   r   rH   r%   r(   r*   r-   r"   r   r   rc   rc      s            )D((((              r   rc   c                  B    e Zd ZU dZdZded<   dd	Zdd
ZddZddZ	dS )LaunchdServiceManagerzFThin wrapper around the ``launchd_*`` functions in hermes_cli.gateway.r	   r   r   r   r   r   r   c                &    ddl m}  |             d S )Nr   )launchd_start)rO   ru   )r$   r   ru   s      r   r%   zLaunchdServiceManager.start   rf   r   c                &    ddl m}  |             d S )Nr   )launchd_stop)rO   rw   )r$   r   rw   s      r   r(   zLaunchdServiceManager.stop   ri   r   c                &    ddl m}  |             d S )Nr   )launchd_restart)rO   ry   )r$   r   ry   s      r   r*   zLaunchdServiceManager.restart   rl   r   r+   c                "    ddl m}  |            S )Nr   )_probe_launchd_service_running)rO   r{   )r$   r   r{   s      r   r-   z LaunchdServiceManager.is_running   s#    EEEEEE--///r   Nr>   r?   rq   r"   r   r   rs   rs      s}         PP(D((((         0 0 0 0 0 0r   rs   c                  V    e Zd ZU dZdZded<   dddddddZddZddZddZ	ddZ
dS )WindowsServiceManageru  Thin wrapper around ``hermes_cli.gateway_windows`` (Scheduled Task /
    Startup-folder fallback).

    The native Windows backend uses a Scheduled Task rather than a true
    init-system service, but for protocol purposes the lifecycle is the
    same: start / stop / restart / is_running. ``install`` accepts a
    handful of Windows-specific kwargs (start_now, start_on_login,
    elevated_handoff) that are passed straight through — non-Windows
    callers should never invoke ``install`` on this wrapper.
    r
   r   r   FNforce	start_nowstart_on_loginelevated_handoffr   r+   r   bool | Noner   r   r   r   c               D    ddl m} |                    ||||           d S )Nr   gateway_windowsr~   )
hermes_clir   install)r$   r   r   r   r   r   s         r   r   zWindowsServiceManager.install  sI     	/.....)-	 	  	
 	
 	
 	
 	
r   r   r   c                :    ddl m} |                                 d S Nr   r   )r   r   r%   r$   r   r   s      r   r%   zWindowsServiceManager.start  s,    ......r   c                :    ddl m} |                                 d S r   )r   r   r(   r   s      r   r(   zWindowsServiceManager.stop  s,    ......r   c                :    ddl m} |                                 d S r   )r   r   r*   r   s      r   r*   zWindowsServiceManager.restart  s,    ......!!!!!r   c                t    ddl m} ddlm} |                                sdS t           |                      S )Nr   r   )find_gateway_pidsF)r   r   rO   r   is_installedr+   )r$   r   r   r   s       r   r-   z WindowsServiceManager.is_running  sW    ......888888++-- 	5%%''(((r   )
r   r+   r   r   r   r   r   r+   r   r   r>   r?   )rD   rE   rF   rG   r   rH   r   r%   r(   r*   r-   r"   r   r   r}   r}      s         	 	  )D((((
 !%&*!&
 
 
 
 
 
           " " " ") ) ) ) ) )r   r}   c                     t                      } | dk    rt                      S | dk    rt                      S | dk    rt                      S | dk    rt	                      S t          d          )zReturn the ServiceManager instance for the current environment.

    Raises:
        RuntimeError: when no supported backend is available.
    r   r	   r
   r   z%no supported service manager detected)rQ   rc   rs   r}   S6ServiceManagerRuntimeError)r   s    r   get_service_managerr   &  sx     "##Dy$&&&y$&&&y$&&&t||!!!
>
?
??r   z/run/servicegateway-z/commandi'  svc_dirr   c                   ddl dfd	} || d
z  d           | dz  } ||d            ||d
z  d           |dz  }|                                s^                    |d           |                    d           	                     |t
          t                     n# t          $ r Y nw xY w| dz  }|                                r ||d
z  d           |dz  } ||d            ||d
z  d           |dz  }|                                sb                    |d           |                    d           	                     |t
          t                     dS # t          $ r Y dS w xY wdS dS )u  Pre-create the ``supervise/`` and top-level ``event/`` skeleton
    inside a service directory, owned by the hermes user.

    Why this exists
    ---------------
    When s6-supervise spawns a service it tries to ``mkdir`` two
    directories: ``<svc>/event`` and ``<svc>/supervise``, both with mode
    ``0700``. It also ``mkfifo``s ``<svc>/supervise/control`` with mode
    ``0600``. Because s6-supervise runs as PID 1's effective UID (root)
    these dirs end up root-owned mode 0700, and an unprivileged client
    (the ``hermes`` user — UID 10000 — running every Hermes runtime
    operation via ``s6-setuidgid``) gets ``EACCES`` on any ``s6-svc``,
    ``s6-svstat``, or ``s6-svwait`` invocation against the slot.

    The PR #30136 review surfaced this as a real product gap: the
    entire S6ServiceManager lifecycle (``register/start/stop/unregister
    _profile_gateway``) was inert in production because every operation
    is dispatched as the hermes user.

    Why this works
    --------------
    Reading s6's source (src/supervision/s6-supervise.c::trymkdir +
    control_init): the ``mkdir`` and ``mkfifo`` calls both treat
    ``EEXIST`` as success. If the directory is already present, the
    chown/chmod fix-up that would normally make event/ ``03730
    root:root`` is **skipped** entirely — s6-supervise just opens the
    pre-existing FIFOs and proceeds. So if we lay the skeleton down
    with hermes ownership before triggering ``s6-svscanctl -a``,
    s6-supervise inherits our layout and never touches it.

    Layout produced
    ---------------
    ``svc_dir/``                           hermes:hermes, 0755 (parent must already exist)
    ``svc_dir/event/``                     hermes:hermes, 03730   (setgid + g+rwx + sticky)
    ``svc_dir/supervise/``                 hermes:hermes, 0755
    ``svc_dir/supervise/event/``           hermes:hermes, 03730
    ``svc_dir/supervise/control``          hermes:hermes, 0660    (FIFO)

    The ``death_tally``, ``lock``, and ``status`` regular files end up
    written by s6-supervise itself (as root), but those land mode 0644 —
    world-readable — and ``s6-svstat`` only needs read access, so the
    hermes user reads them fine.

    If ``svc_dir/log/`` is present (the canonical s6 logger pattern —
    one s6-supervise instance per service, plus a second for its
    logger), the same skeleton is seeded under ``log/`` as well:
    ``log/event/``, ``log/supervise/``, ``log/supervise/event/``,
    ``log/supervise/control``. Without this, unregister teardown
    would EACCES on the logger's supervise dir even after the parent
    slot's supervise/ was hermes-owned.

    Idempotency
    -----------
    Safe to call against a directory where the skeleton already exists.
    Existing entries are left untouched (the helper doesn't try to
    re-chown / re-chmod live FIFOs that s6-supervise may have already
    opened).

    Reference
    ---------
    Discussed at length on the skarnet `skaware` mailing list in 2020
    (`<http://skarnet.org/lists/skaware/1424.html>`_); see also
    just-containers/s6-overlay#130. The pre-creation pattern was
    historically called out as forward-compatibility-fragile, but the
    EEXIST handling in s6-supervise has been stable since 2015 — it's
    the same pattern ``s6-svperms`` and ``fix-attrs.d`` rely on.
    r   Npathr   modeintr   r   c                    |                                  rd S |                     dd           |                     |           	                     | t          t
                     d S # t          $ r Y d S w xY w)NF)parentsexist_ok)existsmkdirchmodchown_HERMES_UID_HERMES_GIDPermissionError)r   r   oss     r   _mkdir_ownedz._seed_supervise_skeleton.<locals>._mkdir_owned  s    ;;== 	F

55
111

4	HHT;44444 	 	 	
 DD	s   !A( (
A65A6eventi  	supervise  controli  log)r   r   r   r   r   r   )	r   r   mkfifor   r   r   r   r   rW   )r   r   r   r   log_dirlog_superviselog_controlr   s          @r   _seed_supervise_skeletonr   _  s   H III       L7"F+++ +%ILE"""LW$f--- )#G>> 
		'5!!!e	HHWk;7777 	 	 	D	 oG~~ Ww&///+-]E***]W,f555#i/!!## 	IIk5)))e$$$k;?????"    	 	s$    !B" "
B/.B/?!E" "
E0/E0c                  *     e Zd ZdZddd fd
Z xZS )S6Errora
  Base error for S6ServiceManager lifecycle failures.

    Concrete subclasses carry the slot name (and, where useful, the
    underlying subprocess output) so the CLI can render an actionable
    message instead of leaking a raw ``CalledProcessError`` traceback.
    Nservicemessager   r   
str | Noner   r   c               X    t                                          |           || _        d S r!   )super__init__r   )r$   r   r   	__class__s      r   r   zS6Error.__init__  s&    !!!r   )r   r   r   r   r   r   rD   rE   rF   rG   r   __classcell__r   s   @r   r   r     sW          ?C            r   r   c                  $     e Zd ZdZd fdZ xZS )GatewayNotRegisteredErroraM  Raised when a lifecycle method targets a slot that doesn't exist.

    Most commonly: ``hermes -p typo gateway start`` when no profile
    ``typo`` exists. Carries the unprefixed profile name (not the
    full ``gateway-<profile>`` service-dir name) so callers can phrase
    a user-facing message like "no such gateway 'typo'".
    r3   r   r   r   c                p    || _         t                                          d|d| dd|            d S )Nzno such gateway z*: register it with `hermes profile create z9` first, or pass an existing profile name via `-p <name>`r   r   )r3   r   r   )r$   r3   r   s     r   r   z"GatewayNotRegisteredError.__init__  se    7w 7 7&-7 7 7 )w((	 	 	
 	
 	
 	
 	
r   rB   r   r   s   @r   r   r     sG         
 
 
 
 
 
 
 
 
 
r   r   c                  $     e Zd ZdZd fd
Z xZS )S6CommandErroru  Raised when an s6 command fails for a reason other than a
    missing slot — e.g. permission denied on the supervise control
    FIFO, or s6-svc returning a non-zero exit for an unexpected
    reason. Carries the stderr from the failing command so callers
    can surface it.
    r   r   action
returncoder   stderrr   r   c                   || _         || _        || _        d| d|d| d}|                                r|d|                                 z  }t	                                          ||           d S )Nzs6-svc z on z failed (rc=r   z: r   )r   r   r   rU   r   r   )r$   r   r   r   r   r   r   s         r   r   zS6CommandError.__init__
  s     $FfFF'FFFFF 	 <<>> 	-,FLLNN,,,G'22222r   )
r   r   r   r   r   r   r   r   r   r   r   r   s   @r   r   r     sG         3 3 3 3 3 3 3 3 3 3r   r   c                      e Zd ZU dZdZded<   efd%d	Zd&dZd'dZ	e
d(d            Ze
d'd            Zd)dZd*dZd+dZd*dZd*dZd,dZd-dZddd.d!Zd/d"Zd0d$ZdS )1r   zPer-profile gateway supervision via s6-overlay.

    Only handles runtime-registered services under
    ``S6_DYNAMIC_SCANDIR``. Static services (main-hermes, dashboard)
    are managed by s6-rc at image-build time and are out of scope.
    r   r   r   scandirr   r   r   c                    || _         d S r!   )r   )r$   r   s     r   r   zS6ServiceManager.__init__"  s    r   r3   r   c                F    t          |           | j        t           | z  S r!   )r   r   S6_SERVICE_PREFIXr9   s     r   _service_dirzS6ServiceManager._service_dir'  s)    g&&&|!2=G====r   c                    t            | S r!   )r   r9   s     r   _service_namezS6ServiceManager._service_name+  s    #.W...r   r2   dict[str, str]c           	        ddl }g d}t          |                                          D ]3\  }}|                    d| d|                    |                      4|                    d           | dk    rd}nd	|                    |            d
}|                    d|            |                    d|            d                    |          dz   S )u  Generate the run script for a profile-gateway s6 service.

        The script:
          1. Sources HERMES_HOME (and any extra env) via with-contenv —
             so e.g. ``-e HERMES_HOME=/data/hermes`` is honored at run
             time, not Python-substituted at registration time (OQ8-C).
          2. Resets ``HOME`` to ``/opt/data`` before the privilege drop
             so with-contenv's root HOME does not leak into the
             unprivileged gateway process.
          3. Activates the bundled venv.
          4. Drops to the hermes user and exec's
             ``hermes -p <profile> gateway run`` (or just ``hermes
             gateway run`` for the default profile — see below).

        Special case: ``profile == "default"`` emits ``hermes gateway
        run`` with **no** ``-p`` flag. This is the sentinel for "the
        root HERMES_HOME profile" (the implicit profile that exists at
        the top of $HERMES_HOME, not under profiles/). It must be
        spelled this way because ``_profile_suffix()`` returns the
        empty string for the root profile, and the dispatcher in
        ``hermes_cli.gateway`` maps that empty string to the
        ``gateway-default`` service slot. Passing ``-p default`` here
        would instead look up ``$HERMES_HOME/profiles/default/`` — a
        completely different (and almost always nonexistent) profile.

        Port selection: the gateway picks its bind port from the
        profile's ``config.yaml`` (``[gateway] port = ...``) — that
        is the single source of truth. Previously this method took a
        ``port`` parameter that was passed in but never substituted
        into the rendered script (it was carried in for "API parity"
        with a deterministic SHA-256 allocator in
        ``hermes_cli.profiles._allocate_gateway_port``). PR #30136
        review item I5 retired both the allocator and the parameter
        because they were dead code through the entire stack.
        r   N)z#!/command/with-contenv shz# shellcheck shell=shzset -ezexport HOME=/opt/datazcd /opt/dataz . /opt/hermes/.venv/bin/activatezexport =z#export HERMES_S6_SUPERVISED_CHILD=1defaultzhermes gateway runz
hermes -p z gateway runz[ "$(id -u)" = 0 ] || exec zexec s6-setuidgid hermes 
)shlexsorteditemsappendquotejoin)r3   r2   r   lineskvgateway_cmds          r   _render_run_scriptz#S6ServiceManager._render_run_script.  s   P 	
 
 
 9??,,-- 	9 	9DAqLL7177u{{1~~778888 	:;;;i.KKIu{{7';';IIIK 	@;@@AAA>>>???yy$&&r   c                @    ddl }|                    |           }d| dS )u  Generate the log/run script for a profile-gateway service.

        OQ8-C: persist to ``${HERMES_HOME}/logs/gateways/<profile>/``.
        CRITICAL: the HERMES_HOME path is sourced from the runtime env
        via with-contenv — NOT Python-substituted at registration time
        — so a container started with ``-e HERMES_HOME=/data/hermes``
        gets its logs under /data/hermes/logs/..., not the build-time
        default.

        Output routing — the script is two action directives, applied
        per line, in order:

          1. ``1`` (forward to stdout) — propagates the line up the
             s6-supervise pipeline to /init's stdout, which is the
             container's stdout, which is ``docker logs``. Without
             this, supervised stdout would be terminated inside
             s6-log and never reach the container's log stream;
             users would have to ``docker exec`` and ``tail`` the
             file just to see startup banners. (Python's ``logging``
             module defaults to stderr, which s6-supervise leaves
             unfiltered — so warnings/errors already reach docker
             logs. This change is specifically about the rich-console
             banner output and other plain stdout writes.)
          2. ``T <log_dir>`` — also write a timestamped copy to the
             rotated log directory (``current`` + archived ``@*.s``
             files). This is what ``hermes logs`` reads and what
             persists across container restarts via the volume mount.

        ``T`` is non-sticky: it only prefixes lines for the next
        action directive. We deliberately put ``T`` between ``1``
        and the log dir (not before ``1``) so:

          * ``docker logs`` shows raw lines — Python's logging
            formatter has its own timestamps, and ``docker logs
            --timestamps`` adds a third layer when desired. No
            double-stamping in the most common reading path.
          * The persisted file gets s6-log's own ISO 8601 timestamp
            so even output that lacked a Python-logger timestamp
            (rich banners, third-party libs' raw prints) is
            correlatable in ``current``.
        r   Nzs#!/command/with-contenv sh
# shellcheck shell=sh
: "${HERMES_HOME:=/opt/data}"
log_dir="$HERMES_HOME/logs/gateways/z"
mkdir -p "$log_dir"
chown -R hermes:hermes "$log_dir" 2>/dev/null || true
[ "$(id -u)" = 0 ] || exec s6-log 1 n10 s1000000 T "$log_dir"
exec s6-setuidgid hermes s6-log 1 n10 s1000000 T "$log_dir"
)r   r   )r3   r   profs      r   _render_log_runz S6ServiceManager._render_log_runs  sA    V 	{{7##M 48M M M
	
r   action_flagaction_labelr   c                   ddl }| j        |z  }|                                sG|                    t                    r|t          t                    d         n|}t          |          	 |                    t           d|t          |          gdddd           dS # |j
        $ r%}t          |||j        |j        pd          |d}~ww xY w)	ug  Shared lifecycle dispatch for start / stop / restart.

        Translates the two failure modes operators care about into
        named errors:

        * ``GatewayNotRegisteredError`` — the service directory at
          ``<scandir>/<name>/`` doesn't exist. ``s6-svc`` would
          exit non-zero with a fairly opaque message; we pre-empt
          it with a clear "no such gateway 'X'" tied to the profile
          name (without the ``gateway-`` prefix).
        * ``S6CommandError`` — anything else (EACCES on the
          supervise control FIFO, timeout, etc.). Carries the
          subprocess return code and stderr so callers can render
          them inline.

        ``action_flag`` is the ``s6-svc`` flag (``-u`` / ``-d`` /
        ``-t``); ``action_label`` is the human verb (``start`` /
        ``stop`` / ``restart``) used in error messages.
        r   N/s6-svcT   )checkcapture_outputtexttimeout )r   r   r   r   )
subprocessr   rW   
startswithr   r   r   run_S6_BIN_DIRr   CalledProcessErrorr   r   r   )r$   r   r   r   r   service_dirr3   excs           r   _run_svczS6ServiceManager._run_svc  s   ( 	lT)!!## 	5
 ??#455S*++,,-- 
 ,G444	NN(((+s;7G7GH4dA       , 	 	 	 #>z'R	  
 	s   +2B 
C) C		Cc                4    |                      dd|           dS )a$  Bring up a registered service (``s6-svc -u``).

        Raises:
            GatewayNotRegisteredError: no service directory for ``name``.
            S6CommandError: s6-svc exited non-zero for any other reason
                (permission denied on the supervise FIFO, timeout, etc.).
        z-ur%   Nr   r#   s     r   r%   zS6ServiceManager.start  s      	dGT*****r   
int | Nonec                H   ddl }	 |                    t           dt          | j        |z            gddd          }n# t
          |j        f$ r Y dS w xY w|j        dk    rdS t          j	        d|j
                  }|r"t          |                    d                    ndS )	a  Return the PID of the supervised gateway process, or None.

        Parses ``s6-svstat`` output (``up (pid NNNN) ...``). Used to
        mark an operator-initiated stop with the planned-stop marker so
        the gateway's shutdown handler classifies the incoming SIGTERM
        as intentional rather than an unexpected kill (issue #42675).
        Best-effort: any parse/exec failure returns None.
        r   N
/s6-svstatTr   r   r   r   z\(pid (\d+)\)   )r   r   r   r   r   rV   SubprocessErrorr   researchstdoutr   group)r$   r   r   resultms        r   _supervised_pidz S6ServiceManager._supervised_pid  s     		^^+++S1D-E-EF#$ $  FF 34 	 	 	44	!!4I&66"#-s1771::-s   8? AAc                    |                      |          }|#	 ddlm}  ||           n# t          $ r Y nw xY w|                     dd|           dS )u  Bring down a registered service (``s6-svc -d``).

        Writes a planned-stop marker naming the supervised gateway PID
        BEFORE sending the down command, so the gateway's shutdown
        handler recognises this SIGTERM as an operator-initiated stop
        and persists ``gateway_state=stopped`` (respecting the explicit
        intent). Without the marker, an intentional ``hermes gateway
        stop`` is indistinguishable from the container/s6 SIGTERM sent on
        ``docker restart``; the latter must NOT persist ``stopped`` or
        container_boot refuses to auto-start on the next boot (#42675).
        The marker write is best-effort — a failure only means the stop
        is treated as signal-initiated, which is the safe fallback.

        Raises:
            GatewayNotRegisteredError: no service directory for ``name``.
            S6CommandError: s6-svc exited non-zero for any other reason.
        Nr   )write_planned_stop_marker-dr(   )r  gateway.statusr  	Exceptionr   )r$   r   pidr  s       r   r(   zS6ServiceManager.stop  s    $ ""4((?DDDDDD))#....   dFD)))))s   + 
88c                4    |                      dd|           dS )zRestart a registered service (``s6-svc -t`` = SIGTERM).

        Raises:
            GatewayNotRegisteredError: no service directory for ``name``.
            S6CommandError: s6-svc exited non-zero for any other reason.
        -tr*   Nr   r#   s     r   r*   zS6ServiceManager.restart  s      	dIt,,,,,r   r+   c                    ddl }|                    t           dt          | j        |z            gddd          }|j        dk    od|j        v S )z1True iff ``s6-svstat`` reports the service as up.r   Nr   Tr   r   zup )r   r   r   r   r   r   r   )r$   r   r   r   s       r   r-   zS6ServiceManager.is_running"  si    '''T\D-@)A)ABdA   
 
  A%@%6=*@@r   c                    dS )NTr"   r/   s    r   r0   z.S6ServiceManager.supports_runtime_registration-  s    tr   Nr1   r4   c               F   ddl }ddl}|                     |          }|                                rt	          d|d|           |                    |j        dz             }|                                r|                    |d           |                    d           	 |d	z  	                    d
           | 
                    ||pi           }|dz  }|	                    |           |                    d           |dz  }	|	                                 |	dz  }
|
	                    |                     |                     |
                    d           t          |           |                    |           n&# t          $ r |                    |d            w xY w|                    t"           ddt%          | j                  gddd          }|j        dk    r5|                    |d           t+          d|j        p|j                   dS )u  Create the s6 service directory for a profile gateway.

        Triggers ``s6-svscanctl -a`` so s6-svscan picks the new directory
        up immediately. The service is created in the *up* state — to
        register without auto-starting, follow up with ``stop(profile)``
        (or pass the start flag via the future ``start_now=False`` arg,
        which the Phase 4 reconciliation path uses via a ``down``
        marker file written directly).

        Raises:
            ValueError: if the profile name is invalid or the service
                directory already exists.
            RuntimeError: if ``s6-svscanctl`` fails.
        r   Nzprofile gateway z already registered at z.tmpTignore_errors)r   r_   zlongrun
r   r   r   /s6-svscanctlz-ar   r   zs6-svscanctl failed: )shutilr   r   r   r   	with_namer   rmtreer   
write_textr   r   r   r   renamer  r   r   r   r   r   r   r   r   )r$   r3   r2   r  r   r   tmp_dir
run_scriptrun_path
log_subdirlog_runr   s               r   r7   z)S6ServiceManager.register_profile_gateway0  si   ( 	##G,,>> 	N7NNWNN   ##GL6$9::>> 	7MM'M666d###	v))+66600)/rJJJH
+++NN5!!! !5J 5(Gt33G<<===MM%    %W---NN7#### 	 	 	MM'M666	
 ***D#dl2C2CDdA   
 
 !! MM'M666H(FHH  	 "!s   &CF #F(c           	        ddl }ddl}ddl}|                     |          }|                                sdS |                    t           ddt          |          gdddd           |                    t           d	d
ddt          |          gdddd           |                    t           ddt          | j                  gdddd           |	                    d           |
                    |d           dS )a  Stop the profile gateway service and remove its directory.

        Idempotent: absent services are a no-op. Best-effort stop +
        wait-for-down before removal so the running gateway process
        gets a chance to shut down cleanly before its service dir
        disappears.

        Teardown ordering matters: ``s6-svscanctl -an`` is fired
        **before** ``rmtree`` so s6-svscan reaps the supervise child
        process (releasing its handle on ``supervise/lock`` and the
        regular files inside the supervise dir), giving us a clean
        directory to remove. Without the reap-first ordering, the
        rmtree races s6-supervise on a set of root-owned files inside
        the supervise dir and the dir is left half-removed.
        r   Nr   r  Tr   F)r   r   r   r   z
/s6-svwaitz-Dr
  10000   r  z-ang?r  )r  r   timer   r   r   r   r   r   sleepr  )r$   r3   r  r   r  r   s         r   r:   z+S6ServiceManager.unregister_profile_gateway  sC     	##G,,~~ 	F 	$$$dCLL9dA 	 	
 	
 	
 	'''tWc'llKdB 	 	
 	
 	
 	***E3t|3D3DEdA 	 	
 	
 	
 	

3 	gT22222r   r;   c                   | j                                         sg S g }| j                                         D ]}|j                            d          r|                                s2|j                            t                    sR|                    |j        t          t                    d                    |S )zReturn the profile names of all currently-registered gateway services.

        Filters the scandir to entries that match the ``gateway-`` prefix.
        Other services (e.g. ``s6-linux-init-shutdownd``) are ignored.
        .N)	r   r   iterdirr   r   rW   r   r   r   )r$   profilesentrys      r   r=   z&S6ServiceManager.list_profile_gateways  s     |""$$ 	I \))++ 	A 	AEz$$S)) <<>> :(():;; OOEJs+<'='='>'>?@@@@r   )r   r   r   r   )r3   r   r   r   )r3   r   r   r   )r3   r   r2   r   r   r   )r   r   r   r   r   r   r   r   r>   )r   r   r   r   r?   r@   rA   rB   rC   )rD   rE   rF   rG   r   rH   S6_DYNAMIC_SCANDIRr   r   r   staticmethodr   r   r   r%   r  r(   r*   r-   r0   r7   r:   r=   r"   r   r   r   r     s           $D####'9     
> > > >/ / / / B' B' B' \B'H 6
 6
 6
 \6
t, , , ,\+ + + +. . . ..* * * *8- - - -A A A A    ,0	M M M M M M^>3 >3 >3 >3@     r   r   r>   )r   r   r@   )r   r   )r   r   r   r   )"rG   
__future__r   r   pathlibr   typingr   r   r   r   compiler   r   r   r   rQ   rP   rZ   rc   rs   r}   r   r%  r   r   r   r   r   r   r   r   r   r   r"   r   r   <module>r+     s     # " " " " " 				       7 7 7 7 7 7 7 7 7 7JK  BJ788  
 
 
 
( 6 6 6 6 6X 6 6 6>! ! ! !H, , , ,\       6    9   80 0 0 0 09 0 0 0,/) /) /) /) /)9 /) /) /)d@ @ @ @> T.))    @ @ @ @F
 
 
 
 
l 
 
 

 
 
 
 
 
 
 
&3 3 3 3 3W 3 3 3,x x x x x x x x x xr   