Publi

Cómo empaquetar programas para Ubuntu o Debian y servirlos desde mi PPA de Launchpad

Hace unos meses vi una serie de posts de El Atareao donde hablaba del empaquetado de un programa para Ubuntu y subirlo a un PPA. Esto nos va a permitir distribuir nuestros programas para que puedan ser instalados fácilmente. Porque, para nosotros programadores, es muy fácil decir que descomprimamos un archivo y cada uno se lo compile en su ordenador. Pero, en realidad, poca gente se lo va a compilar y poca gente lo va a utilizar. Incluso otros programadores, antes de meterse a ver el código de tu programa, preferirían instalárselo en su ordenador y ver cómo funciona. Por otro lado, no todo el mundo tiene instaladas todas las utilidades para poder compilarlo, es más, en servidores, se recomienda, por seguridad, no tener entornos de compilación instalados.

Mención obligatoria

Antes de nada, recomiendo mirar los posts de El Atareao donde habla de estos temas:

En este post, repetiré algunos de los pasos que se mencionan en esos posts, aunque también haré algunas cosas de forma diferente para adaptar el proceso a mis necesidades.

Creando una clave para firmar paquetes

Por seguridad, todos los paquetes deberán ir firmados. Para ello, crearemos claves que garantizarán que el paquete es nuestro. Realizan una correspondencia entre el paquete y el creador. Además, ya que utilizaremos launchpad.net como plataforma para nuestros paquetes, en launchpad se verificará esta identidad, por lo que nadie podrá «hacerse pasar por nosotros» y publicar un paquete en nuestro nombre que no sea nuestro.

Para ello, en nuestro ordenador ejecutamos:

gpg --gen-key
gpg (GnuPG) 1.4.20; Copyright (C) 2015 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Seleccione el tipo de clave deseado:
(1) RSA y RSA (por defecto)
(2) DSA y ElGamal (por defecto)
(3) DSA (sólo firmar)
(4) RSA (sólo firmar)
¿Su elección?
1
las claves RSA pueden tener entre 1024 y 4096 bits de longitud.
¿De qué tamaño quiere la clave? (2048)
2048
El tamaño requerido es de 2048 bits
Especifique el período de validez de la clave.
0 = la clave nunca caduca
= la clave caduca en n días
w = la clave caduca en n semanas
m = la clave caduca en n meses
y = la clave caduca en n años
¿Validez de la clave (0)?
0
La clave nunca caduca
¿Es correcto? (s/n)
s
Necesita un identificador de usuario para identificar su clave. El programa
construye el identificador a partir del Nombre Real, Comentario y Dirección
de Correo electrónico de esta forma:
«Heinrich Heine (Der Dichter) »
Nombre y apellidos:
Gaspar Fernández
Dirección de correo electrónico:
micorreo@midominio.com
Comentario:
Sigueme en Twitter @gaspar_fm
Ha seleccionado este ID de usuario:
«Gaspar Fernández (Sigueme en Twitter @gaspar_fm) »
¿Cambia (N)ombre, (C)omentario, (D)irección o (V)ale/(S)alir?
v

A continuación, el sistema nos preguntará una contraseña dos veces, ya sea en modo texto o en modo gráfico. Tras eso, se generarán las claves RSA necesarias y se añadirá la clave a nuestro repositorio de claves PGP (aunque los programas de GNU para gestionar estas claves se llaman gpg)

Tras eso, podemos ver el listado de claves con la siguiente instrucción

gpg -K
sec   2048R/0333D788 2018-03-18
uid                  Gaspar Fernández (Sigueme en Twitter @gaspar_fm)
ssb   2048R/D59AA557 2018-03-18

Donde 033D788 es la ID de nuestra clave, y que debemos utilizar más adelante. También debemos enviar la clave al servidor de claves de Ubuntu. Esto lo podemos hacer así:

gpg --keyserver keyserver.ubuntu.com --send-keys 0333D788
gpg: enviando clave 0333D788 a hkp servidor keyserver.ubuntu.com

Ahora debemos subid la clave a Launchpad, para ello, dentro de nuestro panel de usuario, editamos OpenPGP keys:
OpenPGP

Luego nos pedirá el fingerprint de la clave, que podemos obtener con:

gpg --fingerprint

Pegando el fingerprint de nuestra clave en la web obtendremos una confirmacion de este tipo:
OpenPGP confirmación.
Una vez hecho esto recibiremos un e-mail cifrado que debemos abrir con la URL para verificar nuestra clave.

Un programa en C

Como el principal uso que le quiero dar a la herramienta es la instalación de programas compilados, voy a empezar creando un programa en C para empezar desde un punto de vista práctico. Algo así como un hola mundo. De todas formas, veremos que no varía mucho la creación del repositorio para un programa sencillo que para un programa más complicado. Por otro lado, debo destacar la facilidad que nos da launchpad.net para crear nuestros repositorios y compilar los programas automáticamente para varias plataformas y versiones. Esto evita que tengamos que tener varias máquinas virtuales con varias versiones del sistema operativo en 32, 64bit y herramientas para poder generar código para ARM para compilar nuestros programas.

El programa será holadist.c:

1
2
3
4
5
6
7
8
9
#include <stdio.h>

#define VERSION "0.1.1"

int main(int argc, char* argv[])
{
        printf ("Hola Distribuible. Versión %s\n", VERSION);
        return 0;
}

Lo podremos compilar haciendo:

gcc -o holadist holadist.c

Como vemos, todo el tema de versión está dentro del mismo programa, aunque podríamos sacarlo fuera en un archivo .h para hacer más fáciles las modificaciones. Bueno, ahora vamos a crear un archivo Makefile para construir nuestro programa. El fichero es un poco general y lo he sacado de una plantilla que utilizo para varios proyectos pequeños. Si tienes un proyecto más grande, seguramente utilices autotools, Cmake, Scons u otra herramienta:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
### Makefile ---

## Author: Gaspar Fernández <blakeyed@totaki.com>
## Keywords:
## X-URL:

CC=gcc
CFLAGS=-O4
LIBS=

SOURCES=holadist.c
INCLUDES=
OBJECTS=$(SOURCES:.c=.o)

EXECUTABLE=holadist

all: $(SOURCES) $(EXECUTABLE)

$(EXECUTABLE): $(OBJECTS)
    $(CC) $(CFLAGS) $(INCLUDES) $(OBJECTS) $(LIBS) -o $@

clean:
    rm -rf $(OBJECTS)
    rm -rf $(EXECUTABLE)

### Makefile ends here

En este punto si hacemos:

make

Se creará el ejecutable.

En nuestro programa, será muy importante que tengamos en cuenta la versión que estamos empaquetando. Será útil cuando creemos actualizaciones del programa, para que los sistemas sepan que tienen la última y para solucionar muchos temas de dependencias. En nuestro caso, la versión es 0.1.1. Por lo tanto, nuestro programa, lo guardaremos en un directorio llamado holadist-0.1.1 (nombre_del_programa-versión). Y si no se te ocurre número para la versión siempre puedes utilizar la fecha en la que lanzaste la última actualización en formato YYYYMMDD o, lo que es lo mismo, los cuatro dígitos del año, seguidos de dos dígitos del mes y por último los dos dígitos del día. El formato es importante, ya que este número será mayor cuanto más en el futuro esté la fecha, así las herramientas de Debian no se harán un lío buscando versiones.

Configurar paquete

Para comenzar, como las herramientas serán en línea de comandos, vamos a establecer el valor de dos variables importantes, DEBEMAIL y DEBFULLNAME que corresponderán con nuestro correo electrónico y nuestro nombre, como desarrolladores o mantenedores del paquete. Podemos hacer lo siguiente:

export DEBEMAIL=»micorreo@midominio.com
export DEBFULLNAME=»Gaspar Fernandez»

Estas dos líneas también podemos ponerlas en nuestro archivo ~/.bashrc y se cargarán siempre que accedamos al sistema. Ahora, dentro del directorio holadist-0.1.1 ejecutamos loo siguiente:
dh_make -c mit --createorig
Type of package: (single, indep, library, python)
[s/i/l/p]?
s
Email-Address       : micorreo@midominio.com
License             : mit
Package Name        : holadist
Maintainer Name     : Gaspar Fernandez
Version             : 0.1.1
Package Type        : single
Date                : Sun, 18 Mar 2018 19:28:05 +0100
Are the details correct? [Y/n/q]
Y
Done. Please edit the files in the debian/ subdirectory now.

En el anterior comando, el parámetro -c mit indica la licencia con la que distribuimos nuestro programa, en este caso, licencia MIT y –createorig indica que se creará automáticamente un archivo comprimido con los archivos del paquete.
Esto ha creado un directorio llamado debian con muchos archivos dentro:

En principio, podemos eliminar todos los archivos que terminen en .ex, son ejemplos de acciones que podemos crear en la instalación de nuestro programa, como arrancarlo al inicio, tener página de manual, ejecutar algo tras la instalación, añadir una entrada en cron, etc:
rm debian/*.ex

Ahora vamos a centrarnos en los archivos que nos quedan, los editaremos y retocaremos para adaptarlos a nuestro proyecto.

changelog:

1
2
3
4
5
holadist (0.1.1-1) xenial; urgency=medium

  * Hola Mundo listo para distribuir

 -- Gaspar Fernandez <micorreo@midominio.com>  Sun, 18 Mar 2018 19:57:34 +0100

Nota: donde pongo xenial debemos colocar la distribución contra la que queremos construir los ejecutables. En mi caso uso Xenial al ser LTS. Pero podemos construir para zesty o con cualquier distribución de Ubuntu. Es posible que deseemos que nuestro paquete funcione también en distribuciones derivadas de Ubuntu o incluso Debian. Tenemos que tener en cuenta el software y las bibliotecas instaladas en cada sistema (yo recurro a máquinas virtuales para probar esto). Con derivados de Ubuntu puede ser fácil porque suelen utilizar las mismas versiones de las bibliotecas que Ubuntu, aunque con Debian podemos tener algún problema, eso lo debemos resolver experimentalmente, o leyendo la documentación de bibliotecas incluidas en la distribución.

control:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Source: holadist
Section: misc  
Priority: extra  
Maintainer: Gaspar Fernandez <micorreo@midominio.com>
Build-Depends: debhelper (>=9)
Standards-Version: 3.9.6
Homepage: https://poesiabinaria.net
#Vcs-Git: git://anonscm.debian.org/collab-maint/holadist.git
#Vcs-Browser: https://anonscm.debian.org/cgit/collab-maint/holadist.git

Package: holadist
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: Un paquete de ejemplo
 No es más que un pequeño ejemplo en C

copyright (se adjunta la licencia que hemos seleccionado, en mi caso, la MIT, aunque debemos rellenar algunas cosas que faltan):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: holadist
Source: <https://poesiabinaria.net>

Files: *
Copyright: 2018 Gaspar Fernández <micorreo@midominio.com>

License: MIT

Files: debian/*
Copyright: 2018 Gaspar Fernandez <micorreo@midominio.com>
License: MIT

License: MIT
 Permission is hereby granted, free of charge, to any person obtaining a
 copy of this software and associated documentation files (the "Software"),
 to deal in the Software without restriction, including without limitation
 the rights to use, copy, modify, merge, publish, distribute, sublicense,
 and/or sell copies of the Software, and to permit persons to whom the
 Software is furnished to do so, subject to the following conditions:
 .
 The above copyright notice and this permission notice shall be included
 in all copies or substantial portions of the Software.
 .
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

# Please also look if there are files or directories which have a
# different copyright/license attached and list them here.
# Please avoid picking licenses with terms that are more restrictive than the
# packaged work, as it may make Debian's contributions unacceptable upstream.

Creando el paquete

En el mismo directorio en el que estamos, haremos:

debuild -S -sa -K033D788

Recordemos que 033D788 es la ID de nuestra clave PGP. Además, con -S creamos un paquete de código fuente.

Al ejecutar esta línea, en una parte del proceso nos preguntará la contraseña de nuestra firma (la que elegimos cuando creamos la clave PGP).

Como vemos, si listamos los archivos, tenemos varios archivos pertenecientes a nuestro proyecto:

-rw-r–r– 1 gaspy gaspy 2364 mar 18 21:26 holadist_0.1.1-1.debian.tar.xz
-rw-r–r– 1 gaspy gaspy 1339 mar 18 21:26 holadist_0.1.1-1.dsc
-rw-r–r– 1 gaspy gaspy 1726 mar 18 21:26 holadist_0.1.1-1_source.changes
-rw-r–r– 1 gaspy gaspy 2039 mar 18 21:26 holadist_0.1.1-1_source.build

Debemos tener presentes estos archivos de cara a subir el paquete a launchpad.net

Enviando nuestra clave a Launchpad

Esto sólo lo tendremos que hacer una vez
Finalmente, para subir nuestro paquete a launchpad, lo haremos con dput, de la siguiente manera:

dput ppa:gasparfm/sysadmins holadist_0.1.1-1_source.changes
Checking signature on .changes
gpg: Firmado el dom 18 mar 2018 21:26:36 CET usando clave RSA ID 0444C699
gpg: Firma correcta de «Gaspar Fernández (https://poesiabinaria.net) »
Good signature on /home/gaspy/Proyectos/git/build/holadist_0.1.1-1_source.changes.
Checking signature on .dsc
gpg: Firmado el dom 18 mar 2018 21:26:33 CET usando clave RSA ID 0444C699
gpg: Firma correcta de «Gaspar Fernández (https://poesiabinaria.net) »
Good signature on /home/gaspy/Proyectos/git/build/holadist_0.1.1-1.dsc.
Uploading to ppa (via ftp to ppa.launchpad.net):
Uploading holadist_0.1.1-1.dsc: done.
Uploading holadist_0.1.1.orig.tar.xz: done.
Uploading holadist_0.1.1-1.debian.tar.xz: done.
Uploading holadist_0.1.1-1_source.changes: done.
Successfully uploaded packages.

En cuestión de segundos deberíamos recibir un correo electrónico diciendo si el archivo a subir ha sido aceptado o no. Cuando se acepta la subida, empezará a compilar. Este proceso puede tardar varios minutos si se trata de un código sencillo. El programa de ejemplo ha tardado unos 10 minutos en compilarse. Tras eso, se marcan como pendientes de publicación, un proceso que puede tardar otros 10 minutos. Si no ha sido aceptado, siempre podemos corregir los errores que hayan sucedido (los recibimos por correo). Si hay errores al compilar, recibiremos un correo indicando que hay errores. Los errores los podemos ver en nuestro panel de launchpad, dentro del paquete y a su vez, dentro de la arquitectura para la que ha sido compilado (puede que haya fallado para compilar en 64bit, pero no en 32bit).

Si este proceso ha fallado, siempre podemos repetir el empaquetado con debuild y enviar con dput. Podemos utilizar dput -f si no cambiamos la versión de nuestro paquete para que nos permita subirlo de nuevo. Si hay errores al compilar, debemos primero eliminar el paquete en launchpad.net. Normalmente la compilación debemos hacerla primero en local, y asegurarnos de que todo funciona, aunque cuando llega a launchpad siempre puede fallar algún tema de dependencias o si estamos compilando para una versión de Ubuntu diferente a la nuestra.

Otro modo de hacer la instalación

Hasta ahora hemos hecho la instalación desde el fichero Makefile. Pero si no estamos utilizando Makefile para construir nuestro programa, queremos separar la instalación de archivos, o nuestro programa no requiere un Makefile, pueden ser scripts, iconos o cualquier cosa que queramos instalar. Debian, y todas las distribuciones con dpkg/apt controlan muy bien la instalación, y no nos tendremos que preocupar de implementar nada para las desinstalaciones, ya se encarga el sistema de todo.
La instalación podemos hacerla también desde el fichero debian/install.

El formato del archivo debian/install es muy sencillo. Simplemente tendremos que poner el archivo con la ruta relativa del archivo que queremos copiar a la ruta del sistema donde vamos a copiarlo, ya sea de forma absoluta o también relativa (El sistema nos pondrá el / delante). El ejemplo de debian/install en nuestro proyecto será el siguiente:

1
holadist usr/bin

Siento holadist el fichero de nuestro programa compilado y /usr/bin el directorio donde vamos a copiar dicho archivo.

Agilizando el proceso para actualizar versiones

Si cada vez que lanzamos una nueva versión tenemos que crear un nuevo paquete y personalizar los archivos del directorio debian sería un trabajo muy duro. En su lugar, vamos a ver el proceso que seguiríamos para hacer una actualización de nuestros paquetes.

  • Tener una política de versiones clara. Cuando empezamos un proyecto tal vez no lo contemplamos, siempre empezamos por la 0.1, 0.2, etc. Pero a medida que vamos avanzando van surgiendo pequeños detalles que serían más fáciles si tuviéramos una política de versiones establecida. Para mis proyectos casi siempre suelo utilizar X.Y.Z, donde:
    • X es la versión mayor. Donde están la mayoría de las características principales de nuestro roadmap. Empezaremos por la 0, luego la 1, etc. Por ejemplo, la versión 1 incluirá todas las características que pensamos que debería tener la primera versión, pero mientras tanto, estaremos con la 0. En este caso, podemos ir soltando características en las versiones menores, eso ya es cosa nuestra. Por otro lado, también usaremos una versión mayor si vamos a hacer cambios que resultan incompatibles con versiones anteriores. En este caso si, por ejemplo, cambiamos el sistema de configuración a un formato incompatible con versiones anteriores. Entonces, es algo inseguro actualizar automáticamente para los usuarios porque les va a costar trabajo adaptarse.
    • Y es la versión menor. Aquí vamos perfeccionando nuestro programa hasta alcanzar la versión mayor. Añadiendo mejoras y correcciones entre cada versión. Puede haber incompatibilidades, pero serán mínimas. Debería haber cambios relativamente pequeños entre versiones menores, aunque puede que se cuele alguna característica nueva. No debería introducir problemas al actualizar. Aunque esta política puede cambiar.
    • Z es la versión micro, llamada también revisión o parche. Suele existir para hacer cambios muy pequeños o corrección de errores. Debe ser siempre seguro actualizar una versión micro. Es más, debe ser recomendable para mantener nuestro programa seguro. Imaginemos que hemos sacado nuestra versión 1.2.0 de un programa, y de repente nos reportan un fallo de seguridad. Dicho fallo estará corregido en la 1.2.1 y todos deberían actualizarse. No siempre tiene que ser por fallos de seguridad o corrección de bugs, también puede ser una mejora en el rendimiento, pero no se suele añadir nuevas funcionalidades. Todo se queda como en la 1.2.0, pero mejor.
    • Las versiones pueden crecer mucho: 1.2.0, 1.9.5, 1.12.9, 1.12.19, etc. Es decir, después de la 1.9 no tiene por qué venir la 2.0, nunca sabremos las revisiones que debemos hacer. Es más, a lo mejor después de la 1.2 viene la 2.0, no tendremos por qué llegar a un número determinado
    • Esta política no es absoluta. Ni todos los programas del mundo la siguen, ni tienen por qué, ni te estoy diciendo que la sigas en tus programas. Es solo una orientación para construir tu propia política de versiones, o evaluar la política que sigue algún programa que te gusta con el fin de llevarla a tu propio software. Eso sí, si mantienes varios programas, y puedes elegir, intenta siempre seguir la misma política porque puede ser un jaleo mental mantener varias políticas a la vez.

    Además, este sistema no se lleva mal con las utilidades de Debian para empaquetar problemas, por lo que se puede utilizar sin problemas.

  • Actualizar el código fuente y asegurarnos de que todo está en su sitio. Parece el paso obvio, en este caso, cuando vayamos a actualizar nuestro programa, seguramente tocaremos varios archivos de código fuente. Pero debemos asegurarnos de que todo compila bien (probar que make y make install si utilizamos este último funcionan perfectamente. Más que nada porque si el proceso de compilación o instalación en launchpad falla, vamos a perder mucho tiempo en tener un paquete funcional. Ellos tienen muchos paquetes que comprobar y compilar todo el tiempo, así que una compilación normal puede tardar en sus equipos más que en los nuestros.
  • Entramos en el directorio de nuestro programa, ahí podemos ejecutar:
    dch -v 0.1.3 -D xenial

    Para empezar la nueva versión 0.1.3 de nuestro programa. Da igual que nuestro directorio tenga otra versión, dch se encargará de renombrar el directorio y hacer todos los cambios pertinentes al archivo. Con -D xenial especificamos que hacemos una versión para Ubuntu Xenial.

    Otra forma de hacer esto, con mucho más control es utilizar:

    dch -U

    Con esto actualizaremos el changelog añadiendo una nueva versión (automáticamente cogerá la siguiente versión a la que tenemos, si estamos en la 0.1.3 cogerá automáticamente la 0.1.4).

    Luego, a cada cambio que introduzcamos, vamos ejecutando dch (a secas) y actualizando el changelog, o si no:

    dch «Comentario»

    Para actualizar el changelog sin entrar en el editor.

    Cuando hayamos terminado de hacer los cambios y estemos preparados para el release, hacemos:

    sch -r «Release»

    Añadimos el comentario release y se cambiará el UNRELEASED del changelog por la distribución a la que pertenecen el resto de comentarios.
  • dh_make -y -s --createorig

    Con esto creamos el archivo tar.xz de nuestro código fuente. Como ya tenemos el directorio debian en nuestro proyecto no se crearán archivos nuevos. Alternativamente, también podríamos hacer lo siguiente (importante el nombre del archivo, con el guión bajo _ para separar la versión, el nombre de paquete al principio y orig.tar.xz para la extensión):
    tar cvjf ../holadist_0.1.3.orig.tar.xz .
  • Preparamos para subir el paquete para Debian con debuild:
    debuild -S -sa -k033D788

    Recordemos que 033D788 es el ID de mi clave PGP. Debéis poner la vuestra.
  • Por último, subimos el paquete Debian a launchpad:
    dput -f ppa:gasparfm/sysadmins ../holadist_0.1.3_source.changes

    Si estoy dentro del directorio de mi proyecto, deberé especificar ../ (directorio superior), pero si voy al directorio superior, con indicar el nombre de archivo vale. Todos estos archivos los podemos ver con nuestro explordor (o con ls)

Todo esto se puede automatizar, y podemos crear scripts que hagan nuestra vida, y la de nuestros programas mucho más fácil.

¿Empaquetas tu software para Debian o Ubuntu?

Ya no tenemos excusa para distribuir nuestros programas, al menos para Ubuntu. Aunque aún quedan algunos puntos por cubrir, podemos empezar con esto y empezar a distribuirlos. Es más, muy pronto, empezaré a soltar algunos de mis programas y utilidades de esta forma 🙂

Referencias

Además de visitar el blog El Atareao y los posts mencionados anteriormente. He sacado bastante información de otros sitios:

Foto principal: unsplash-logoKira auf der Heide

También podría interesarte....

There are 11 comments left Ir a comentario

  1. updatea /
    Usando Google Chrome Google Chrome 24.0.1312.70 en Windows Windows 8

    Gracias.He conectado y me ha gustado mucho.

  2. Nick /
    Usando Google Chrome Google Chrome 116.0.0.0 en Windows Windows NT

    That’s amazing to know and btw, this site is pretty interesting. bow clip

  3. elshoolaa /
    Usando Google Chrome Google Chrome 117.0.0.0 en Windows Windows NT

    إنّ جلسات الحدائق المنزلية هي إضافة جميلة ومريحة لأي منزل، فمن خلالها يمكن للأفراد الاستمتاع بالهواء الطلق ورونق الطبيعة بكل راحة ورفاهية. تتوفر جلسات الحدائق بأشكال متعددة، وتتصف بالتناسب مع مساحات الحدائق والاحتياجات الشخصية لأصحاب المنازل. وتتمتع بجمالية فريدة بفضل تصاميمها العصرية والأصيلة، والألوان المناسبة التي تتناسب مع الأجواء الخارجية. وتتميز هذه الجلسات بالمتانة والقدرة على التحمل لسنوات عديدة، وتتيح للأفراد الجلوس والاسترخاء والاستمتاع بوقتهم برفقة الأصدقاء والعائلة في أجواء مريحة وهادئة. وتعتبر جلسات الحدائق المنزلية إضافة لافتة للنظر إلى المنزل وتمثل استثمارًا جيدًا لمن يرغبون بالاستمتاع بحياتهم في الهواء الطلق دون الحاجة إلى مغادرة المنزل.

    جلسات حدائق منزلية

  4. SPORTS BETTING /
    Usando Google Chrome Google Chrome 117.0.0.0 en Windows Windows NT

    There’s no doubt i would fully rate it after i read what is the idea about this article. You did a nice job.
    nhl betting rules

  5. 먹튀검증 /
    Usando Google Chrome Google Chrome 118.0.0.0 en Windows Windows NT

    A very informative article on how to improve one’s expression, mood, and sentence. Please refer to it here.먹튀검증

  6. 먹튀신고 /
    Usando Google Chrome Google Chrome 119.0.0.0 en Windows Windows NT

    I have read all the comments and suggestions posted by the visitors for this article are very fine,We will wait for your next article so only.Thanks.먹튀신고

  7. super edan /
    Usando Google Chrome Google Chrome 119.0.0.0 en Windows Windows NT

    The inclusion of references Watermelon Game and sources enhances the credibility of the information presented. It’s great to see a commitment to accuracy.

  8. aside /
    Usando Google Chrome Google Chrome 121.0.0.0 en Windows Windows NT

    If you like to watch sex movies, you are welcome to our site. Milf Porn

  9. Emilia Mia /
    Usando Google Chrome Google Chrome 122.0.0.0 en Windows Windows NT

    It’s great to see El Atareao sharing insights on packaging programs for Ubuntu and utilizing PPAs for easy distribution. As programmers, it’s crucial to prioritize user experience by providing simple installation methods. By following these guidelines, we can ensure that our programs reach a wider audience without the hassle of manual compilation. This only enhances accessibility but also fosters a sense of community around our projects. Contact experienced maths coursework help writers can offer valuable support and expertise for those seeking assistance. Let’s continue to streamline the user experience and make our software more accessible to everyone.

  10. Insurance /
    Usando Google Chrome Google Chrome 123.0.0.0 en Windows Windows NT

    Hi there! Someone in my Facebook group shared this site with us so I came to look it over. I’m definitely loving the information. I’m book-marking and will be tweeting this to my followers!

    Excellent blog and amazing design.

  11. Giovanni /
    Usando Google Chrome Google Chrome 123.0.0.0 en Windows Windows NT

    Thank you for your kind words! I’m here to help whenever you need it. If you have any questions or need assistance in the future, feel free to reach out. Wishing you continued success and happiness as well. Take care, and have a fantastic day! Looking forward to our next interaction too.

    VIP bodyguard services in New York City provide specialized protection for high-profile individuals, celebrities, executives, dignitaries, and other individuals who face elevated security risks. These services are tailored to meet the specific needs and preferences of VIP clients and often include a range of security measures designed to ensure their safety and well-being. Here are some key aspects of VIP bodyguard services in New York City:Personal Security Guard New York

Leave a Reply to Anónimo Cancle Reply