diff --git a/README.md b/README.md
index 99ee954501098efcccb598f30e4f7a715d6ba998..43ce86f8d193748b50029a3708ef921250b43271 100644
--- a/README.md
+++ b/README.md
@@ -150,6 +150,8 @@ Mémo pour développeurs: séquence re-création d'une base:
 
 # Paquet debian 11
 
-Ce que le script d'installation/mise à jour du paquet ne fait pas encore:
+Les scripts associés au paquet Debian (.deb) sont dans `tools/debian`.
+
+La préparation d'une release se fait à l'aide du script
+`tools/build_release.sh`. 
 
- - migrations flask (à faire)
diff --git a/app/models/etudiants.py b/app/models/etudiants.py
index 674e9e8ca223ce332167d51f76bd800b0a0ae993..cf37a77f4c7cf3a4ea195cf5af622e403ba53bf6 100644
--- a/app/models/etudiants.py
+++ b/app/models/etudiants.py
@@ -40,6 +40,8 @@ class Identite(db.Model):
     # Codes INE et NIP pas unique car le meme etud peut etre ds plusieurs dept
     code_nip = db.Column(db.Text())
     code_ine = db.Column(db.Text())
+    # Ancien id ScoDoc7 pour les migrations de bases anciennes
+    scodoc7_id = db.Column(db.Text(), nullable=True)
 
 
 class Adresse(db.Model):
diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py
index e01b25e5d7e189674611699241427792e4a540f4..f02a19c75ebebad884c9e7eac73f0fa90d414a80 100644
--- a/app/models/formsemestre.py
+++ b/app/models/formsemestre.py
@@ -69,6 +69,8 @@ class FormSemestre(db.Model):
     etapes = db.relationship(
         "NotesFormsemestreEtape", cascade="all,delete", backref="notes_formsemestre"
     )
+    # Ancien id ScoDoc7 pour les migrations de bases anciennes
+    scodoc7_id = db.Column(db.Text(), nullable=True)
 
     def __init__(self, **kwargs):
         super(FormSemestre, self).__init__(**kwargs)
diff --git a/app/scodoc/sco_archives.py b/app/scodoc/sco_archives.py
index 5567b4c7625d55f6121237dc7695852b0f30a8fa..c9333eeaf82ec46e217c01b6c1ba7246aade22e5 100644
--- a/app/scodoc/sco_archives.py
+++ b/app/scodoc/sco_archives.py
@@ -34,9 +34,11 @@
 
  Les PV de jurys et documents associés sont stockées dans un sous-repertoire de la forme
     <archivedir>/<dept>/<formsemestre_id>/<YYYY-MM-DD-HH-MM-SS>
+ (formsemestre_id est ici FormSemestre.scodoc7_id ou à défaut FormSemestre.id)
 
  Les documents liés à l'étudiant sont dans
     <archivedir>/docetuds/<dept>/<etudid>/<YYYY-MM-DD-HH-MM-SS>
+(etudid est ici soit Identite.scodoc7id, soit à défaut Identite.id)
 
  Les maquettes Apogée pour l'export des notes sont dans
     <archivedir>/apo_csv/<dept>/<annee_scolaire>-<sem_id>/<YYYY-MM-DD-HH-MM-SS>/<code_etape>.csv
@@ -284,7 +286,9 @@ def do_formsemestre_archive(
     """
     from app.scodoc.sco_recapcomplet import make_formsemestre_recapcomplet
 
-    archive_id = PVArchive.create_obj_archive(formsemestre_id, description)
+    sem = sco_formsemestre.get_formsemestre(formsemestre_id)
+    sem_archive_id = sem["scodoc7_id"] or formsemestre_id
+    archive_id = PVArchive.create_obj_archive(sem_archive_id, description)
     date = PVArchive.get_archive_date(archive_id).strftime("%d/%m/%Y à %H:%M")
 
     if not group_ids:
@@ -394,10 +398,11 @@ def formsemestre_archive(REQUEST, formsemestre_id, group_ids=[]):
             init_qtip=True,
         ),
         """<p class="help">Cette page permet de générer et d'archiver tous
-les documents résultant de ce semestre: PV de jury, lettres individuelles, 
-tableaux récapitulatifs.</p><p class="help">Les documents archivés sont 
+les documents résultant de ce semestre: PV de jury, lettres individuelles,
+tableaux récapitulatifs.</p><p class="help">Les documents archivés sont
 enregistrés et non modifiables, on peut les retrouver ultérieurement.
-</p><p class="help">On peut archiver plusieurs versions des documents (avant et après le jury par exemple).
+</p><p class="help">On peut archiver plusieurs versions des documents 
+(avant et après le jury par exemple).
 </p>
         """,
     ]
@@ -495,8 +500,10 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
 
 def formsemestre_list_archives(REQUEST, formsemestre_id):
     """Page listing archives"""
+    sem = sco_formsemestre.get_formsemestre(formsemestre_id)
+    sem_archive_id = sem["scodoc7_id"] or formsemestre_id
     L = []
-    for archive_id in PVArchive.list_obj_archives(formsemestre_id):
+    for archive_id in PVArchive.list_obj_archives(sem_archive_id):
         a = {
             "archive_id": archive_id,
             "description": PVArchive.get_archive_description(archive_id),
@@ -505,7 +512,6 @@ def formsemestre_list_archives(REQUEST, formsemestre_id):
         }
         L.append(a)
 
-    sem = sco_formsemestre.get_formsemestre(formsemestre_id)
     H = [html_sco_header.html_sem_header(REQUEST, "Archive des PV et résultats ", sem)]
     if not L:
         H.append("<p>aucune archive enregistrée</p>")
@@ -537,7 +543,9 @@ def formsemestre_list_archives(REQUEST, formsemestre_id):
 
 def formsemestre_get_archived_file(REQUEST, formsemestre_id, archive_name, filename):
     """Send file to client."""
-    return PVArchive.get_archived_file(REQUEST, formsemestre_id, archive_name, filename)
+    sem = sco_formsemestre.get_formsemestre(formsemestre_id)
+    sem_archive_id = sem["scodoc7_id"] or formsemestre_id
+    return PVArchive.get_archived_file(REQUEST, sem_archive_id, archive_name, filename)
 
 
 def formsemestre_delete_archive(
@@ -548,8 +556,9 @@ def formsemestre_delete_archive(
         raise AccessDenied(
             "opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
         )
-    _ = sco_formsemestre.get_formsemestre(formsemestre_id)  # check formsemestre_id
-    archive_id = PVArchive.get_id_from_name(formsemestre_id, archive_name)
+    sem = sco_formsemestre.get_formsemestre(formsemestre_id)
+    sem_archive_id = sem["scodoc7_id"] or formsemestre_id
+    archive_id = PVArchive.get_id_from_name(sem_archive_id, archive_name)
 
     dest_url = "formsemestre_list_archives?formsemestre_id=%s" % (formsemestre_id)
 
diff --git a/app/scodoc/sco_archives_etud.py b/app/scodoc/sco_archives_etud.py
index c1327c8a9a505a1197759b6ff54c8c42e6fff715..7b21da22a04d5eabd107fca9020505a393a7886c 100644
--- a/app/scodoc/sco_archives_etud.py
+++ b/app/scodoc/sco_archives_etud.py
@@ -27,7 +27,7 @@
 
 """ScoDoc : gestion des fichiers archivés associés aux étudiants
      Il s'agit de fichiers quelconques, généralement utilisés pour conserver
-     les dossiers d'admission et autres pièces utiles. 
+     les dossiers d'admission et autres pièces utiles.
 """
 import flask
 from flask import url_for, g
@@ -39,7 +39,7 @@ from app.scodoc import sco_trombino
 from app.scodoc import sco_excel
 from app.scodoc import sco_archives
 from app.scodoc.sco_permissions import Permission
-from app.scodoc.sco_exceptions import AccessDenied
+from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
 from app.scodoc.TrivialFormulator import TrivialFormulator
 from app.scodoc import html_sco_header
 from app.scodoc import sco_etud
@@ -61,8 +61,13 @@ def can_edit_etud_archive(authuser):
 def etud_list_archives_html(REQUEST, etudid):
     """HTML snippet listing archives"""
     can_edit = can_edit_etud_archive(REQUEST.AUTHENTICATED_USER)
+    etuds = sco_etud.get_etud_info(etudid=etudid)
+    if not etuds:
+        raise ScoValueError("étudiant inexistant")
+    etud = etuds[0]
+    etud_archive_id = etud["scodoc7_id"] or etudid
     L = []
-    for archive_id in EtudsArchive.list_obj_archives(etudid):
+    for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
         a = {
             "archive_id": archive_id,
             "description": EtudsArchive.get_archive_description(archive_id),
@@ -113,7 +118,8 @@ def add_archives_info_to_etud_list(etuds):
     """
     for etud in etuds:
         l = []
-        for archive_id in EtudsArchive.list_obj_archives(etud["etudid"]):
+        etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
+        for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
             l.append(
                 "%s (%s)"
                 % (
@@ -129,9 +135,12 @@ def etud_upload_file_form(REQUEST, etudid):
     # check permission
     if not can_edit_etud_archive(REQUEST.AUTHENTICATED_USER):
         raise AccessDenied(
-            "opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
+            "opération non autorisée pour %s" % REQUEST.AUTHENTICATED_USER
         )
-    etud = sco_etud.get_etud_info(filled=True)[0]
+    etuds = sco_etud.get_etud_info(filled=True)
+    if not etuds:
+        raise ScoValueError("étudiant inexistant")
+    etud = etuds[0]
     H = [
         html_sco_header.sco_header(
             page_title="Chargement d'un document associé à %(nomprenom)s" % etud,
@@ -172,18 +181,21 @@ def etud_upload_file_form(REQUEST, etudid):
         data = tf[2]["datafile"].read()
         descr = tf[2]["description"]
         filename = tf[2]["datafile"].filename
-        _store_etud_file_to_new_archive(etudid, data, filename, description=descr)
+        etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
+        _store_etud_file_to_new_archive(
+            etud_archive_id, data, filename, description=descr
+        )
         return flask.redirect(
             url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
         )
 
 
-def _store_etud_file_to_new_archive(etudid, data, filename, description=""):
+def _store_etud_file_to_new_archive(etud_archive_id, data, filename, description=""):
     """Store data to new archive."""
     filesize = len(data)
     if filesize < 10 or filesize > scu.CONFIG.ETUD_MAX_FILE_SIZE:
         return 0, "Fichier image de taille invalide ! (%d)" % filesize
-    archive_id = EtudsArchive.create_obj_archive(etudid, description)
+    archive_id = EtudsArchive.create_obj_archive(etud_archive_id, description)
     EtudsArchive.store(archive_id, filename, data)
 
 
@@ -194,8 +206,12 @@ def etud_delete_archive(REQUEST, etudid, archive_name, dialog_confirmed=False):
         raise AccessDenied(
             "opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
         )
-    etud = sco_etud.get_etud_info(filled=True)[0]
-    archive_id = EtudsArchive.get_id_from_name(etudid, archive_name)
+    etuds = sco_etud.get_etud_info(filled=True)
+    if not etuds:
+        raise ScoValueError("étudiant inexistant")
+    etud = etuds[0]
+    etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
+    archive_id = EtudsArchive.get_id_from_name(etud_archive_id, archive_name)
     if not dialog_confirmed:
         return scu.confirm_dialog(
             """<h2>Confirmer la suppression des fichiers ?</h2>
@@ -228,7 +244,14 @@ def etud_delete_archive(REQUEST, etudid, archive_name, dialog_confirmed=False):
 
 def etud_get_archived_file(REQUEST, etudid, archive_name, filename):
     """Send file to client."""
-    return EtudsArchive.get_archived_file(REQUEST, etudid, archive_name, filename)
+    etuds = sco_etud.get_etud_info(filled=True)
+    if not etuds:
+        raise ScoValueError("étudiant inexistant")
+    etud = etuds[0]
+    etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
+    return EtudsArchive.get_archived_file(
+        REQUEST, etud_archive_id, archive_name, filename
+    )
 
 
 # --- Upload d'un ensemble de fichiers (pour un groupe d'étudiants)
@@ -260,17 +283,22 @@ def etudarchive_import_files_form(group_id, REQUEST=None):
             page_title="Import de fichiers associés aux étudiants"
         ),
         """<h2 class="formsemestre">Téléchargement de fichier associés aux étudiants</h2>
-        <p>Les fichiers associés (dossiers d'admission, certificats, ...), de types quelconques (pdf, doc, images)
-        sont accessibles aux utilisateurs via la fiche individuelle de l'étudiant.
+        <p>Les fichiers associés (dossiers d'admission, certificats, ...), de 
+        types quelconques (pdf, doc, images) sont accessibles aux utilisateurs via 
+        la fiche individuelle de l'étudiant.
         </p>
-        <p class="warning">Ne pas confondre avec les photos des étudiants, qui se chargent via l'onglet "Photos".</p>
-         <p><b>Vous pouvez aussi charger à tout moment de nouveaux fichiers, ou en supprimer, via la fiche de chaque étudiant.</b></p>
-         <p class="help">Cette page permet de charger en une seule fois les fichiers de plusieurs étudiants.<br/>
-          Il faut d'abord remplir une feuille excel donnant les noms 
+        <p class="warning">Ne pas confondre avec les photos des étudiants, qui se
+        chargent via l'onglet "Photos".</p>
+         <p><b>Vous pouvez aussi charger à tout moment de nouveaux fichiers, ou en
+        supprimer, via la fiche de chaque étudiant.</b>
+         </p>
+         <p class="help">Cette page permet de charger en une seule fois les fichiers
+        de plusieurs étudiants.<br/>
+          Il faut d'abord remplir une feuille excel donnant les noms
           des fichiers (un fichier par étudiant).
          </p>
-         <p class="help">Ensuite, réunir vos fichiers dans un fichier zip, puis télécharger 
-         simultanément le fichier excel et le fichier zip.
+         <p class="help">Ensuite, réunir vos fichiers dans un fichier zip, puis
+        télécharger simultanément le fichier excel et le fichier zip.
          </p>
         <ol>
         <li><a class="stdlink" href="etudarchive_generate_excel_sample?group_id=%s">
diff --git a/app/scodoc/sco_etud.py b/app/scodoc/sco_etud.py
index 7915b0af6d47c96d9bf8c56b4a7cc25cf16dd7eb..365ebc4d915803e83ab0ce740ddb690d07f5c1d7 100644
--- a/app/scodoc/sco_etud.py
+++ b/app/scodoc/sco_etud.py
@@ -256,6 +256,7 @@ _identiteEditor = ndb.EditableTable(
         "photo_filename",
         "code_ine",
         "code_nip",
+        "scodoc7_id",
     ),
     filter_dept=True,
     sortkey="nom",
@@ -655,9 +656,9 @@ def make_etud_args(etudid=None, code_nip=None, use_request=True, raise_exc=True)
     return args
 
 
-def get_etud_info(etudid=False, code_nip=False, filled=False):
-    """infos sur un etudiant (API)
-    On peut specifier etudid ou conde_nip
+def get_etud_info(etudid=False, code_nip=False, filled=False) -> list:
+    """infos sur un etudiant (API). If not foud, returns empty list.
+    On peut specifier etudid ou code_nip
     ou bien cherche dans REQUEST.form: etudid, code_nip, code_ine
     (dans cet ordre).
     """
diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py
index 64fd6dc61759917a562cb599595f33df584a6495..eadc20dcc8686558dcd66f956d8a5e1d19eb109e 100644
--- a/app/scodoc/sco_formsemestre.py
+++ b/app/scodoc/sco_formsemestre.py
@@ -67,6 +67,7 @@ _formsemestreEditor = ndb.EditableTable(
         "ens_can_edit_eval",
         "elt_sem_apo",
         "elt_annee_apo",
+        "scodoc7_id",
     ),
     filter_dept=True,
     sortkey="date_debut",
diff --git a/migrations/versions/017e32eb4773_scodoc_9_0_4_ajout_id_scodoc7_pour_.py b/migrations/versions/017e32eb4773_scodoc_9_0_4_ajout_id_scodoc7_pour_.py
new file mode 100644
index 0000000000000000000000000000000000000000..41f5f29a394968ea9c51497c34d6624d55f06c8c
--- /dev/null
+++ b/migrations/versions/017e32eb4773_scodoc_9_0_4_ajout_id_scodoc7_pour_.py
@@ -0,0 +1,30 @@
+"""ScoDoc 9.0.4: ajout id scodoc7 pour migrations (archives)
+
+Revision ID: 017e32eb4773
+Revises: 6b071b7947e5
+Create Date: 2021-08-27 21:58:05.317092
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '017e32eb4773'
+down_revision = '6b071b7947e5'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.add_column('identite', sa.Column('scodoc7_id', sa.Text(), nullable=True))
+    op.add_column('notes_formsemestre', sa.Column('scodoc7_id', sa.Text(), nullable=True))
+    # ### end Alembic commands ###
+
+
+def downgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.drop_column('notes_formsemestre', 'scodoc7_id')
+    op.drop_column('identite', 'scodoc7_id')
+    # ### end Alembic commands ###
diff --git a/tools/import_scodoc7_dept.py b/tools/import_scodoc7_dept.py
index 960376fa17d6c018d4284645edc69896fcfafa84..1e642ee593e903f520a8667dafff2846a00e2ed8 100644
--- a/tools/import_scodoc7_dept.py
+++ b/tools/import_scodoc7_dept.py
@@ -260,6 +260,8 @@ def convert_object(
     if id_name:
         old_id = obj[id_name]
         del obj[id_name]
+        if hasattr(klass, "scodoc7_id"):
+            obj["scodoc7_id"] = old_id
     else:
         old_id = None  # tables ScoDoc7 sans id
     if is_table:
@@ -283,6 +285,7 @@ def convert_object(
         if (k.endswith("id") or k == "object") and k not in USER_REFS | {
             "semestre_id",
             "sem_id",
+            "scodoc7_id",
         }:
             old_ref = obj[k]
             if old_ref is not None: